#include "linux/tty.h"
#include "asm/io.h"

#define MEM_BASE 0xB8000 // 显存基址

#define CRT_ADDR_REG 0x3d4 // CRT Controller Registers - Address Register
#define CRT_DATA_REG 0x3d5 // CRT Controller Registers - Data Register

#define CRT_START_ADDR_H 0xc // 显示内存起始位置 - Start Address High
#define CRT_START_ADDR_L 0xd // 显示内存起始位置 - Start Address Low

#define CRT_CURSOR_H 0xe // 光标位置 - Cursor High
#define CRT_CURSOR_L 0xf // 光标位置 - Cursor Low

#define MEM_SIZE 0x4000 // 显存大小

#define WIDTH 80 // 显示区域宽度
#define HEIGHT 25 // 显示区域高度
#define ROW_SIZE (WIDTH * 2) // 每行字节数
#define SCR_SIZE (ROW_SIZE * HEIGHT) // 显示区域字节数
#define MEM_END (MEM_BASE + MEM_SIZE) // 显存结束地址

#define ASCII_NUL 0x00
#define ASCII_ENQ 0x05
#define ASCII_BEL 0x07 // \a
#define ASCII_BS 0x08  // \b
#define ASCII_HT 0x09  // \t
#define ASCII_LF 0x0A  // \n
#define ASCII_VT 0x0B  // \v
#define ASCII_FF 0x0C  // \f
#define ASCII_CR 0x0D  // \r
#define ASCII_DEL 0x7F

static uint screen;
static uint pos;
static uint x, y;

static void set_cursor(void)
{
    out_byte(CRT_ADDR_REG, CRT_CURSOR_H);
    out_byte(CRT_DATA_REG, ((pos - MEM_BASE) >> 9) & 0xff);
    out_byte(CRT_ADDR_REG, CRT_CURSOR_L);
    out_byte(CRT_DATA_REG, ((pos - MEM_BASE) >> 1) & 0xff);
}

static void set_screen(void)
{
    out_byte(CRT_ADDR_REG, CRT_START_ADDR_H);
    out_byte(CRT_DATA_REG, ((screen - MEM_BASE) >> 9) & 0xff);
    out_byte(CRT_ADDR_REG, CRT_START_ADDR_L);
    out_byte(CRT_DATA_REG, ((screen - MEM_BASE) >> 1) & 0xff);

}

void console_clear(void)
{
    screen = MEM_BASE;
    pos = MEM_BASE;
    x = 0;
    y = 0;

    // 设置光标位置
    set_cursor();

    // 设置屏幕
    set_screen();


    // 把显示区域全部置为空格
    u16* p = (u16*)screen;
    while (p < MEM_BASE + MEM_SIZE)
    {
        *p++ = 0x0720;
    }

}

void console_init(void)
{
    console_clear();
}

static void scroll_up(void)
{
    if (screen + SCR_SIZE + ROW_SIZE < MEM_END)
    {
        u32 *ptr = (u32 *)(screen + SCR_SIZE);
        for (size_t i = 0; i < WIDTH; i++)
        {
            *ptr++ = 0x0720;
        }
        screen += ROW_SIZE;
        pos += ROW_SIZE;
    }
    else
    {
        // memcpy(MEM_BASE, screen, SCR_SIZE);
        pos -= (screen - MEM_BASE);
        screen = MEM_BASE;
    }
    set_screen();
}

// 换行符，只切换行，不把光标移到行首
static void command_lf(void)
{
    // 如果当前行不是最后一行
    if (y + 1 < HEIGHT)
    {
        y++;
        pos += ROW_SIZE;
        return;
    }
    // 如果是最后一行需要翻屏
    scroll_up();
}

// 回车符，把光标移到行首
static void command_cr(void)
{
    pos -= x << 1;
    x = 0;
}

// 退格，删除光标前一个字符，这里没有考虑退到上一行的情况
static void command_bs()
{
    if (x)
    {
        x--;
        pos -= 2;
        *(u16 *)pos = 0x0720;
    }
}

// delete，删除光标后一个字符，这里没有考虑退到下一行的情况
static void command_del()
{
    *(u16 *)pos = 0x0720;
}

void console_write(char* str, u32 count)
{
    char ch;
    char *ptr = (char *) pos;
    while (count--) 
    {
        ch = *str++;
        switch (ch)
        {
            case ASCII_NUL:
                break;
            case ASCII_BEL:
                break;
            case ASCII_BS:
                command_bs();
                break;
            case ASCII_HT:
                break;
            case ASCII_LF:
                command_lf();
                command_cr();
                break;
            case ASCII_VT:
                break;
            case ASCII_FF:
                command_lf();
                break;
            case ASCII_CR:
                command_cr();
                break;
            case ASCII_DEL:
                command_del();
                break;
            default:
                if (x >= WIDTH)
                {
                    x -= WIDTH;
                    pos -= ROW_SIZE;
                    command_lf();
                }

                *ptr = ch;
                ptr++;
                *ptr = 0x07;
                ptr++;

                pos += 2;
                x++;
                break;
        }
    }
    set_cursor();
}